// MIT License

// Copyright (c) 2019 Erin Catto

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef B2_FIXTURE_H
#define B2_FIXTURE_H

#include "b2_api.h"
#include "b2_body.h"
#include "b2_collision.h"
#include "b2_shape.h"

class b2BlockAllocator;
class b2Body;
class b2BroadPhase;
class b2Fixture;

/// This holds contact filtering data.
struct B2_API b2Filter
{
  b2Filter()
  {
    categoryBits = 0x0001;
    maskBits = 0xFFFF;
    groupIndex = 0;
  }

  /// The collision category bits. Normally you would just set one bit.
  uint16 categoryBits;

  /// The collision mask bits. This states the categories that this
  /// shape would accept for collision.
  uint16 maskBits;

  /// Collision groups allow a certain group of objects to never collide (negative)
  /// or always collide (positive). Zero means no collision group. Non-zero group
  /// filtering always wins against the mask bits.
  int16 groupIndex;
};

/// A fixture definition is used to create a fixture. This class defines an
/// abstract fixture definition. You can reuse fixture definitions safely.
struct B2_API b2FixtureDef
{
  /// The constructor sets the default fixture definition values.
  b2FixtureDef()
  {
    shape = nullptr;

#ifdef ENABLE_FRICTION
    friction = 0.2f;
#endif // ENABLE_FRICTION

#ifdef ENABLE_RESTITUTION
    restitution = 0.0f;
    restitutionThreshold = 1.0f * b2_lengthUnitsPerMeter;
#endif // ENABLE_RESTITUTION

    density = 0.0f;
    isSensor = false;
  }

  /// The shape, this must be set. The shape will be cloned, so you
  /// can create the shape on the stack.
  const b2Shape* shape;
  
#ifdef ENABLE_USER_DATA
  /// Use this to store application specific fixture data.
  b2FixtureUserData userData;
#endif // ENABLE_USER_DATA

#ifdef ENABLE_FRICTION
  /// The friction coefficient, usually in the range [0,1].
  float friction;
#endif // ENABLE_FRICTION

#ifdef ENABLE_RESTITUTION
  /// The restitution (elasticity) usually in the range [0,1].
  float restitution;
#endif // ENABLE_RESTITUTION

  /// Restitution velocity threshold, usually in m/s. Collisions above this
  /// speed have restitution applied (will bounce).
  float restitutionThreshold;

  /// The density, usually in kg/m^2.
  float density;

  /// A sensor shape collects contact information but never generates a collision
  /// response.
  bool isSensor;

  /// Contact filtering data.
  b2Filter filter;
};

/// A fixture is used to attach a shape to a body for collision detection. A fixture
/// inherits its transform from its parent. Fixtures hold additional non-geometric data
/// such as friction, collision filters, etc.
/// Fixtures are created via b2Body::CreateFixture.
/// @warning you cannot reuse fixtures.
class B2_API b2Fixture
{
public:
  /// Get the type of the child shape. You can use this to down cast to the concrete shape.
  /// @return the shape type.
  b2Shape::Type GetType() const;

  /// Get the child shape. You can modify the child shape, however you should not change the
  /// number of vertices because this will crash some collision caching mechanisms.
  /// Manipulating the shape may lead to non-physical behavior.
  b2Shape* GetShape();
  const b2Shape* GetShape() const;

  /// Set if this fixture is a sensor.
  void SetSensor(bool sensor);

  /// Is this fixture a sensor (non-solid)?
  /// @return the true if the shape is a sensor.
  bool IsSensor() const;

  /// Set the contact filtering data. This will not update contacts until the next time
  /// step when either parent body is active and awake.
  /// This automatically calls Refilter.
  void SetFilterData(const b2Filter& filter);

  /// Get the contact filtering data.
  const b2Filter& GetFilterData() const;

  /// Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide.
  void Refilter();

  /// Get the parent body of this fixture. This is nullptr if the fixture is not attached.
  /// @return the parent body.
  b2Body* GetBody();
  const b2Body* GetBody() const;

  /// Get the next fixture in the parent body's fixture list.
  /// @return the next shape.
  b2Fixture* GetNext();
  const b2Fixture* GetNext() const;

#ifdef ENABLE_USER_DATA
  /// Get the user data that was assigned in the fixture definition. Use this to
  /// store your application specific data.
  b2FixtureUserData& GetUserData();
#endif // ENABLE_USER_DATA
  
  uint32 GetId();
  
  /// Test a point for containment in this fixture.
  /// @param p a point in world coordinates.
  bool TestPoint(const b2Vec2& p) const;

  /// Compute the distance from this fixture.
  /// @param p a point in world coordinates.
  void ComputeDistance(const b2Vec2& p, float32* distance, b2Vec2* normal) const;

  /// Cast a ray against this shape.
  /// @param output the ray-cast results.
  /// @param input the ray-cast input parameters.
  bool RayCast(b2RayCastOutput* output, const b2RayCastInput& input) const;

  /// Get the mass data for this fixture. The mass data is based on the density and
  /// the shape. The rotational inertia is about the shape's origin. This operation
  /// may be expensive.
  void GetMassData(b2MassData* massData) const;

  /// Set the density of this fixture. This will _not_ automatically adjust the mass
  /// of the body. You must call b2Body::ResetMassData to update the body's mass.
  void SetDensity(float density);

  /// Get the density of this fixture.
  float GetDensity() const;

#ifdef ENABLE_FRICTION
  /// Get the coefficient of friction.
  float GetFriction() const;

  /// Set the coefficient of friction. This will _not_ change the friction of
  /// existing contacts.
  void SetFriction(float friction);
#endif // ENABLE_FRICTION

#ifdef ENABLE_RESTITUTION
  /// Get the coefficient of restitution.
  float GetRestitution() const;

  /// Set the coefficient of restitution. This will _not_ change the restitution of
  /// existing contacts.
  void SetRestitution(float restitution);

  /// Get the restitution velocity threshold.
  float GetRestitutionThreshold() const;

  /// Set the restitution threshold. This will _not_ change the restitution threshold of
  /// existing contacts.
  void SetRestitutionThreshold(float threshold);
#endif // ENABLE_RESTITUTION
  
  void UpdateAABB();
  
  /// Get the fixture's AABB. This AABB may be enlarge and/or stale.
  /// If you need a more accurate AABB, compute it using the shape and
  /// the body transform.
  const b2AABB& GetAABB() const;

  /// Dump this fixture to the log file.
  void Dump(int32 bodyIndex);

protected:

  friend class b2Body;
  friend class b2World;
  friend class b2Contact;
  friend class b2ContactManager;

  b2Fixture();

  // We need separation create/destroy functions from the constructor/destructor because
  // the destructor cannot access the allocator (no destructor arguments allowed by C++).
  void Create(b2BlockAllocator* allocator, b2Body* body, const b2FixtureDef* def);
  void Destroy(b2BlockAllocator* allocator);
  
  float m_density;

  b2Fixture* m_next;
  b2Body* m_body;

  b2Shape* m_shape;
  b2AABB m_aabb;
  
#ifdef ENABLE_FRICTION
  float m_friction;
#endif // ENABLE_FRICTION

#ifdef ENABLE_RESTITUTION
  float m_restitution;
  float m_restitutionThreshold;
#endif // ENABLE_RESTITUTION
  
  b2Filter m_filter;

  bool m_isSensor;

#ifdef ENABLE_USER_DATA
  b2FixtureUserData m_userData;
#endif // ENABLE_USER_DATA
  
  uint32 m_id;
};

inline b2Shape::Type b2Fixture::GetType() const
{
  return m_shape->GetType();
}

inline b2Shape* b2Fixture::GetShape()
{
  return m_shape;
}

inline const b2Shape* b2Fixture::GetShape() const
{
  return m_shape;
}

inline bool b2Fixture::IsSensor() const
{
  return m_isSensor;
}

inline const b2Filter& b2Fixture::GetFilterData() const
{
  return m_filter;
}

#ifdef ENABLE_USER_DATA
inline b2FixtureUserData& b2Fixture::GetUserData()
{
  return m_userData;
}
#endif // ENABLE_USER_DATA

inline uint32 b2Fixture::GetId()
{
  return m_id;
}

inline b2Body* b2Fixture::GetBody()
{
  return m_body;
}

inline const b2Body* b2Fixture::GetBody() const
{
  return m_body;
}

inline b2Fixture* b2Fixture::GetNext()
{
  return m_next;
}

inline const b2Fixture* b2Fixture::GetNext() const
{
  return m_next;
}

inline void b2Fixture::SetDensity(float density)
{
  b2Assert(b2IsValid(density) && density >= 0.0f);
  m_density = density;
}

inline float b2Fixture::GetDensity() const
{
  return m_density;
}

#ifdef ENABLE_FRICTION
inline float b2Fixture::GetFriction() const
{
  return m_friction;
}

inline void b2Fixture::SetFriction(float friction)
{
  m_friction = friction;
}
#endif // ENABLE_FRICTION

#ifdef ENABLE_RESTITUTION
inline float b2Fixture::GetRestitution() const
{
  return m_restitution;
}

inline void b2Fixture::SetRestitution(float restitution)
{
  m_restitution = restitution;
}
#endif // ENABLE_RESTITUTION

inline float b2Fixture::GetRestitutionThreshold() const
{
  return m_restitutionThreshold;
}

inline void b2Fixture::SetRestitutionThreshold(float threshold)
{
  m_restitutionThreshold = threshold;
}

inline bool b2Fixture::TestPoint(const b2Vec2& p) const
{
  return m_shape->TestPoint(m_body->GetTransform(), p);
}

inline void b2Fixture::ComputeDistance(const b2Vec2& p, float32* d, b2Vec2* n) const
{
  m_shape->ComputeDistance(m_body->GetTransform(), p, d, n);
}

inline bool b2Fixture::RayCast(b2RayCastOutput* output, const b2RayCastInput& input) const
{
  return m_shape->RayCast(output, input, m_body->GetTransform());
}

inline void b2Fixture::GetMassData(b2MassData* massData) const
{
  m_shape->ComputeMass(massData, m_density);
}

inline const b2AABB& b2Fixture::GetAABB() const
{
  return m_aabb;
}

#endif
